package com.liesheng.haylou.utils;

import android.os.Handler;
import android.os.HandlerThread;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;

import com.liesheng.haylou.app.HyApplication;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.Calendar;
import java.util.Locale;
import java.util.TimeZone;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;

/**
 * Date:2019/11/6
 * <p>
 * Author:wuzhiguang
 * <p>
 * Description:
 */
public class LogUtil {

    private static final boolean isDebug = HyApplication.DEBUG_MODE;// 是否需要打印bug，可以在application的onCreate函数里面初始化
    private static final String TAG = "Log";
    private static final long FLUSH_LOG_DELAY = 80;
    private static final int MSG_FLUSH = 0;
    private static boolean sLogcatEnabled;
    private static Handler handler;
    private static LogWriterThread logWriterThread;
    private static final StringBuffer LOG_BUFFER = new StringBuffer();
    private static final int BUFFER_SIZE = 4096;

    private static int sLogLvl = Level.VERBOSE.code;
    private static String sLogLvlName;
    private static String sLogDir;
    private static boolean sInitialized;

    private static boolean write2File = false;//是否写到日志里去
    private static boolean isLogPrint = true;//是否打印日志


    private static String getLogTime() {

        try {
            Calendar calendar = Calendar.getInstance(TimeZone.getDefault(), Locale.getDefault());
            return (calendar.get(Calendar.MONTH)+1)
                    +"-"+calendar.get(Calendar.DAY_OF_MONTH)
                    +" "+calendar.get(Calendar.HOUR_OF_DAY)
                    +":"+calendar.get(Calendar.MINUTE)
                    +":"+calendar.get(Calendar.SECOND)
                    +"."+calendar.get(Calendar.MILLISECOND);
        } catch (Exception e) {
            return ""+ System.currentTimeMillis();
        }
    }

    public static void setLogLevel(Level level){
        sLogLvlName = level.name();
        sLogLvl = level.code;
    }

    public static void setLogDir(String dir){
        sLogDir = dir;
    }

    public static String getLogDir() {
        return sLogDir;
    }

    /**
     * init file logger with level and debug mode.
     * @param level log level
     * @param debugMode debug mode
     */
    public static void init(String logFolder, Level level, boolean debugMode){
        setLogDir(logFolder);
        setLogLevel(level);
        sLogcatEnabled = debugMode;

        logWriterThread = new LogWriterThread(){
            @Override
            protected void onLooperPrepared() {
                super.onLooperPrepared();
                handler = new Handler(getLooper()){
                    @Override
                    public void handleMessage(Message msg) {
                        removeMessages(MSG_FLUSH);
                        logWriterThread.writeLog();
                    }
                };
                LogUtil.i(TAG,"inited level="+ LogUtil.sLogLvlName+",sLogcatEnabled="+ sLogcatEnabled);
                sInitialized = true;
            }
        };
        logWriterThread.start();

        android.util.Log.i(TAG,"initialized... level="+level.name()+",lvl="+sLogLvl+",debug="+ sLogcatEnabled +",dir="+ logFolder);
    }

    public static boolean isInitialized() {
        return sInitialized;
    }

    public static void f(String tag, Throwable t) {
        f(tag, "", t);
    }

    public static void f(String tag, String msg, Throwable t) {
        String trace = getTrace(t);
        if(msg == null)
            f(tag, trace);
        else
            f(tag, msg + "\n" + trace);
    }

    public static void f(String tag, String msg) {
        if(msg == null) return;
        android.util.Log.e(tag, msg);
        writeToFile(tag + " " + msg, Level.FATAL);
    }

    public static void e(String tag, Throwable t) {
        if(sLogLvl < Level.ERROR.code)
            return;
        e(tag, "", t);
    }

    public static void e(String tag, String msg, Throwable t) {
        if(sLogLvl < Level.ERROR.code) return;

        String trace = getTrace(t);

        if(msg == null)
            e(tag, trace);
        else
            e(tag, msg + "\n" + trace);
    }

    public static void e(String tag, String msg) {
        if(sLogLvl < Level.ERROR.code) return;
        if(msg == null) return;
        if(sLogcatEnabled) android.util.Log.e(tag, msg);

        writeToFile(tag + " " + msg, Level.ERROR);
    }

    public static void w(String tag, Throwable t) {
        w(tag, "", t);
    }

    public static void w(String tag, String msg, Throwable t) {
        if(sLogLvl < Level.WARN.code) return;

        String trace = getTrace(t);
        if(msg == null)
            w(tag, trace);
        else
            w(tag, msg + "\n" + trace);
    }

    public static void w(String tag, String msg) {
        if(sLogLvl < Level.WARN.code) return;
        if(msg == null) return;
        if(sLogcatEnabled) android.util.Log.w(tag, msg);

        writeToFile(tag + " " + msg, Level.WARN);
    }

    public static void i(String tag, Throwable t) {
        if(sLogLvl < Level.INFO.code)
            return;
        i(tag, "", t);
    }

    public static void i(String tag, String msg, Throwable t) {
        if(!isLogPrint || sLogLvl < Level.INFO.code)
            return;

        String trace = getTrace(t);
        if(msg == null)
            i(tag, trace);
        else
            i(tag, msg + "\n" + trace);
    }

    public static void i(String tag, String msg) {
        if(!isLogPrint || sLogLvl < Level.INFO.code){
            return;
        }
        if(msg == null) return;
        if(sLogcatEnabled) android.util.Log.i(tag, msg);
        writeToFile(tag + " " + msg, Level.INFO);
    }

    public static void d(String tag, Throwable t) {
        if(!isLogPrint || sLogLvl < Level.DEBUG.code)
            return;
        d(tag, "", t);
    }
    public static void d(String msg) {
        if (isDebug)
            Log.d(TAG, Utils.isEmpry(msg));
    }

    public static void d(String tag, String msg, Throwable t) {
        if(!isLogPrint || sLogLvl < Level.DEBUG.code) return;

        String trace = getTrace(t);
        if(msg == null)
            d(tag, trace);
        else
            d(tag, msg + "\n" + trace);
    }

    public static void d(String tag, String msg) {
        if(!isLogPrint || sLogLvl < Level.DEBUG.code) return;
        if(msg == null) return;
        if(sLogcatEnabled) android.util.Log.d(tag, msg);
        writeToFile(tag + " " + msg, Level.DEBUG);
    }

    public static void v(String tag, Throwable t) {
        if(sLogLvl < Level.VERBOSE.code) return;
        v(tag, "", t);
    }

    public static void v(String tag, String msg, Throwable t) {
        if(sLogLvl < Level.VERBOSE.code)
            return;

        String trace = getTrace(t);
        if(msg == null)
            v(tag, trace);
        else
            v(tag, msg + "\n" + trace);
    }

    public static void v(String tag, String msg) {
        if(sLogLvl < Level.VERBOSE.code) return;
        if(msg == null) return;
        if(sLogcatEnabled) android.util.Log.v(tag, msg);

        writeToFile(tag + " " + msg, Level.VERBOSE);
    }



    private static void writeToFile(String log, Level level) {

        try {
            LOG_BUFFER.append(getLogTime());
            LOG_BUFFER.append(level.prefix);
            LOG_BUFFER.append(log);
            LOG_BUFFER.append("\n");

            if(handler == null){
                android.util.Log.w(TAG," handler not inited log:"+log+"\n");
                return;
            }

            handler.sendEmptyMessageDelayed(MSG_FLUSH, FLUSH_LOG_DELAY);
        } catch (Exception e) {
            android.util.Log.e(TAG,"writeToFile",e);
        }
    }

    private static String getTrace(Throwable t) {
        if(t == null) return "";

        StringWriter stringWriter = new StringWriter();
        PrintWriter writer = new PrintWriter(stringWriter);
        t.printStackTrace(writer);
        StringBuffer buffer = stringWriter.getBuffer();
        return buffer.toString();
    }

    static class LogWriterThread extends HandlerThread {


        boolean isArchiving;

        FileWriter fileWriter;
        BufferedWriter writer;
        String currentFileName;

        Runnable closeRunnable = new Runnable() {
            @Override
            public void run() {
                try {
                    if (writer != null)
                        writer.close();
                } catch (Exception e) {
                } finally {
                    writer = null;
                }
            }
        };

        public LogWriterThread() {
            super("LogWriter", android.os.Process.THREAD_PRIORITY_BACKGROUND);
        }

        public void writeLog() {
            if(sLogDir == null) return;

            try {
                if (writer == null) {
                    File folder = new File(sLogDir);
                    if (!folder.exists() && !folder.mkdirs()){
                        android.util.Log.e(TAG,"log dir create failed.dir="+ sLogDir);
                    }

                    try {
                        String fileName = genLogFileName();
                        if (currentFileName == null)
                            currentFileName = fileName;

                        final File logFile = new File(sLogDir, fileName);
                        if (!logFile.exists()) {
                            boolean created = logFile.createNewFile();
                            if(!created){
                                LogUtil.e(TAG,"create new file failed path="+logFile.getAbsolutePath());
                            }

                            if (!isArchiving && !fileName.equals(currentFileName))
                                compressLog(fileName);

                        }
                        fileWriter = new FileWriter(logFile, true);
                        writer = new BufferedWriter(fileWriter);
                    } catch (Exception e) {
                        android.util.Log.e(TAG, "writeLog", e);
                    }
                }

                if (writer != null && (LOG_BUFFER.length() > 0)) {
                    writer.write(LOG_BUFFER.toString());
                    writer.newLine();
                    writer.flush();
                }

                handler.removeCallbacks(closeRunnable);
                handler.postDelayed(closeRunnable, 150);
            } catch (Exception e) {
                android.util.Log.e(TAG, "writeLog", e);
                writer = null;
            } finally {
                LOG_BUFFER.setLength(0);
            }
        }

        private String genLogFileName() {
            String filename;
            try {
                Calendar cal = Calendar.getInstance();
                filename = cal.get(Calendar.YEAR)
                        + "-" + (cal.get(Calendar.MONTH) + 1)
                        + ("-") + cal.get(Calendar.DAY_OF_MONTH);
            } catch (Exception e) {
                filename = "t"+ SystemClock.elapsedRealtime();
            }
            return filename + ".log";
        }

        private void compressLog(final String newFilename) {
            LogUtil.d(TAG, "compressLog new=" + newFilename + ",current=" + currentFileName);
            if (currentFileName == null) return;
            if (isArchiving) return;

            isArchiving = true;

            new Thread() {
                @Override
                public void run() {
                    try {
                        File file = new File(sLogDir + File.separator + currentFileName);
                        if (file.exists()) {
                            LogUtil.d(TAG, "compressing...");
                            zip(file.getName(), currentFileName.replaceFirst("\\.log", ".zip"));
                            currentFileName = newFilename;
                            if(!file.delete()){
                                LogUtil.w(TAG,"delete log file failed:"+file.getName());
                            }

                        }
                    } catch (Exception e) {
                        LogUtil.e(TAG, "compressLog:" + e.getMessage(), e);
                    }
                    isArchiving = false;
                }
            }.start();

        }

        public void zip(String filename, String zipFileName) {
            android.util.Log.d(TAG, "zip filename:" + filename + ",zip file name:" + zipFileName);
            FileOutputStream dest = null;
            ZipOutputStream out = null;
            FileInputStream fi = null;
            BufferedInputStream origin = null;
            try {
                dest = new FileOutputStream(sLogDir + File.separator + zipFileName);
                out = new ZipOutputStream(new BufferedOutputStream(dest));
                byte data[] = new byte[BUFFER_SIZE];

                fi = new FileInputStream(sLogDir + File.separator + filename);
                origin = new BufferedInputStream(fi, BUFFER_SIZE);

                ZipEntry entry = new ZipEntry(filename.substring(filename.lastIndexOf("/") + 1));
                out.putNextEntry(entry);
                int count;

                while ((count = origin.read(data, 0, BUFFER_SIZE)) != -1) {
                    out.write(data, 0, count);
                }

            } catch (Exception e) {
                android.util.Log.e(TAG, "zip error:",e);
            } finally {

                try { if (fi != null) fi.close();
                } catch (Exception e) {}

                try {
                    if (origin != null) origin.close();
                } catch (Exception e) { }

                try {
                    if (out != null) out.close();
                } catch (Exception e) { }

                try {
                    if (dest != null) dest.close();
                } catch (Exception e) { }
            }
        }

        @Override
        public boolean quit() {
            writeLog();

            if (writer != null) {
                try {
                    writer.close();
                } catch (Exception e) {
                }
            }

            if (fileWriter != null) {
                try {
                    fileWriter.close();
                } catch (Exception e) {
                }
            }
            return super.quit();
        }
    }

    public enum Level {
        FATAL(0," [F] "),
        ERROR(1," [E] "),
        WARN(2," [W] "),
        INFO(3," [I] "),
        DEBUG(4," [D] "),
        VERBOSE(5," [V] ");

        public final String prefix;
        public final int code;

        Level(int code, String prefix) {
            this.code = code;
            this.prefix = prefix;
        }
    }
}
